// Copyright (c) 2022 by Duguang.IO Inc. All Rights Reserved.
// Author: Ethan Liu
// Date: 2022-05-05 12:43:18

package engine

import (
	"github.com/google/uuid"
	v1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/resource"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"path"
	"strings"
)

// 返回命名空间
func toNamespace(name string, labels map[string]string) *v1.Namespace {
	return &v1.Namespace{
		ObjectMeta: metav1.ObjectMeta{
			Name:   name,
			Labels: labels,
		},
	}
}

func toPod(unit *Unit) *v1.Pod {
	return &v1.Pod{
		ObjectMeta: metav1.ObjectMeta{
			Name:        unit.PodSpec.Name,
			Namespace:   unit.PodSpec.Namespace,
			Annotations: unit.PodSpec.Annotations,
			Labels:      unit.PodSpec.Labels,
		},
		Spec: v1.PodSpec{
			ServiceAccountName: unit.PodSpec.ServiceAccountName,
			RestartPolicy:      v1.RestartPolicyNever,
			Volumes:            toVolumes(unit),
			Containers:         toContainers(unit),
			InitContainers:     toInitContainers(unit),
			NodeName:           unit.PodSpec.NodeName,
			NodeSelector:       unit.PodSpec.NodeSelector,
			Tolerations:        toTolerations(unit),
			ImagePullSecrets:   toImagePullSecrets(unit),
			HostAliases:        toHostAliases(unit),
			DNSConfig:          toDnsConfig(unit),
		},
	}
}

func toContainers(unit *Unit) []v1.Container {
	var containers []v1.Container
	for _, s := range unit.Runners {
		containers = append(containers, toContainer(s, unit))
	}
	return containers
}

func toInitContainers(unit *Unit) []v1.Container {
	var containers []v1.Container
	for _, s := range unit.Internal {
		c := toContainer(s, unit)
		c.Image = s.Image
		containers = append(containers, c)
	}
	return containers
}

func toContainer(runner *Runner, unit *Unit) v1.Container {
	command := make([]string, 0)
	if runner.Entrypoint != "" {
		command = append(command, runner.Entrypoint)
	} else {
		command = nil
	}

	return v1.Container{
		Name:            runner.Name,
		Image:           runner.Image,
		Command:         command,
		Args:            runner.Command,
		ImagePullPolicy: toPullPolicy(runner.Pull),
		WorkingDir:      runner.WorkingDir,
		Resources:       *toResources(runner.Resources),
		SecurityContext: toSecurityContext(runner),
		VolumeMounts:    toVolumeMounts(unit, runner),
		Env:             toEnv(unit, runner),
	}
}

// 创建环境变量列表
func toEnv(unit *Unit, runner *Runner) []v1.EnvVar {
	var envVars []v1.EnvVar

	for k := range runner.Envs {
		envVars = append(envVars, v1.EnvVar{
			Name:  k,
			Value: runner.Envs[k],
		})
	}

	envVars = append(envVars, v1.EnvVar{
		Name: "KUBERNETES_NODE",
		ValueFrom: &v1.EnvVarSource{
			FieldRef: &v1.ObjectFieldSelector{
				FieldPath: "spec.nodeName",
			},
		},
	})

	return envVars
}

func toPullPolicy(policy PullPolicy) v1.PullPolicy {
	switch policy {
	case PullAlways:
		return v1.PullAlways
	case PullNever:
		return v1.PullNever
	case PullIfNotExists:
		return v1.PullIfNotPresent
	default:
		return v1.PullIfNotPresent
	}
}

func toResources(src *Resources) *v1.ResourceRequirements {
	return toResourcesInner(src.Limits.CPU, src.Limits.Memory, src.Requests.CPU, src.Requests.Memory)
}

func toResourcesInner(cpuLimits, memoryLimits, cpuRequests, memoryRequests string) *v1.ResourceRequirements {
	var dst v1.ResourceRequirements
	if cpuLimits != "" || memoryLimits != "" {
		dst.Limits = v1.ResourceList{}
		if memoryLimits != "" {
			dst.Limits[v1.ResourceMemory] = resource.MustParse(memoryLimits)
		}
		if cpuLimits != "" {
			dst.Limits[v1.ResourceCPU] = resource.MustParse(cpuLimits)
		}
	}
	if cpuRequests != "" || memoryRequests != "" {
		dst.Requests = v1.ResourceList{}
		if memoryRequests != "" {
			dst.Requests[v1.ResourceMemory] = resource.MustParse(memoryRequests)
		}
		if cpuRequests != "" {
			dst.Requests[v1.ResourceCPU] = resource.MustParse(cpuRequests)
		}
	}
	return &dst
}

func toSecurityContext(runner *Runner) *v1.SecurityContext {
	return &v1.SecurityContext{
		Privileged: boolptr(runner.Privileged),
		RunAsUser:  runner.User,
		RunAsGroup: runner.Group,
	}
}

func toVolumeMounts(unit *Unit, runner *Runner) []v1.VolumeMount {
	var volumeMounts []v1.VolumeMount
	volumes := toVolumes(unit)
	//mountMode := v1.MountPropagationHostToContainer
	for _, volume := range volumes {
		if volume.Name == "result-pvc" {
			mountpath := unit.ResultFile
			if !unit.K8sNode {
				volumeMounts = append(volumeMounts, v1.VolumeMount{
					Name:      volume.Name,
					MountPath: mountpath,
					ReadOnly:  false,
					// SubPath:          path.Base(unit.ResultFile),
					MountPropagation: nil,
				})
			}
		} else if volume.Name == "docker-sock" {
			/*	volumeMounts = append(volumeMounts, v1.VolumeMount{
				Name:             volume.Name,
				MountPath:        "/var/run/docker.sock",
				ReadOnly:         false,
				MountPropagation: nil,
			})*/
		} else if volume.Name == "result-pvc-dir" {
			volumeMounts = append(volumeMounts, v1.VolumeMount{
				Name:             volume.Name,
				MountPath:        "result-" + unit.TaskInstanceId,
				ReadOnly:         false,
				MountPropagation: nil,
			})
		} else if volume.Name == "customm2" {
                        
                        volumeMounts = append(volumeMounts, v1.VolumeMount{
                                Name:             "customm2",
                                MountPath:        "/workspace/.m2/repository",
                                ReadOnly:         false,
                                MountPropagation: nil,
                        })

                } else {
			volumeMounts = append(volumeMounts, v1.VolumeMount{
				Name:             volume.Name,
				MountPath:        unit.Volume.Name,
				ReadOnly:         false,
				MountPropagation: nil,
			})
		}
	}


	return volumeMounts
}

// LookupVolume is a helper function that will lookup
// the id for a volume.
func lookupVolumeID(unit *Unit, name string) (string, bool) {
	for _, v := range unit.Volumes {
		if v.EmptyDir != nil && v.EmptyDir.Name == name {
			return v.EmptyDir.ID, true
		}

		if v.HostPath != nil && v.HostPath.Name == name {
			return v.HostPath.ID, true
		}

		if v.Claim != nil && v.Claim.Name == name {
			return v.Claim.ID, true
		}

		if v.ConfigMap != nil && v.ConfigMap.Name == name {
			return v.ConfigMap.ID, true
		}

		if v.Secret != nil && v.Secret.Name == name {
			return v.Secret.ID, true
		}

		if v.DownwardAPI != nil && v.DownwardAPI.Name == name {
			return v.DownwardAPI.ID, true
		}
	}

	return "", false
}

func toTolerations(unit *Unit) []v1.Toleration {
	var tolerations []v1.Toleration
	for _, toleration := range unit.PodSpec.Tolerations {
		t := v1.Toleration{
			Key:      toleration.Key,
			Operator: v1.TolerationOperator(toleration.Operator),
			Effect:   v1.TaintEffect(toleration.Effect),
			Value:    toleration.Value,
		}
		if toleration.TolerationSeconds != nil {
			t.TolerationSeconds = int64ptr(int64(*toleration.TolerationSeconds))
		}
		tolerations = append(tolerations, t)
	}
	return tolerations
}

func int64ptr(v int64) *int64 {
	return &v
}

func toImagePullSecrets(unit *Unit) []v1.LocalObjectReference {
	var pullSecrets []v1.LocalObjectReference
	if unit.PullSecret != nil {
		pullSecrets = []v1.LocalObjectReference{{
			Name: unit.PullSecret.Name,
		}}
	}
	return pullSecrets
}

func toHostAliases(unit *Unit) []v1.HostAlias {
	var hostAliases []v1.HostAlias
	for _, hostAlias := range unit.PodSpec.HostAliases {
		if len(hostAlias.Hostnames) > 0 {
			hostAliases = append(hostAliases, v1.HostAlias{
				IP:        hostAlias.IP,
				Hostnames: hostAlias.Hostnames,
			})
		}
	}
	return hostAliases
}

func toDnsConfig(unit *Unit) *v1.PodDNSConfig {
	var dnsOptions []v1.PodDNSConfigOption
	if len(unit.PodSpec.DnsConfig.Options) > 0 {
		for _, option := range unit.PodSpec.DnsConfig.Options {
			o := v1.PodDNSConfigOption{
				Name:  option.Name,
				Value: option.Value,
			}
			dnsOptions = append(dnsOptions, o)
		}
	}
	return &v1.PodDNSConfig{
		Nameservers: unit.PodSpec.DnsConfig.Nameservers,
		Searches:    unit.PodSpec.DnsConfig.Searches,
		Options:     dnsOptions,
	}
}

func toVolumes(unit *Unit) []v1.Volume {
	var volumes []v1.Volume
	hostPathType := v1.HostPathDirectoryOrCreate
	volumes = append(volumes,
		v1.Volume{
			Name: "host-path-pvc",
			VolumeSource: v1.VolumeSource{
				HostPath: &v1.HostPathVolumeSource{
					Path: path.Join("/tmp/t",unit.DataDir, unit.Volume.Name),
					Type: &hostPathType,
				},
			},
		})



	// TODO docker socket 配置
	// 需要配置文件
	/*sockPathType := v1.HostPathSocket
		volumes = append(volumes, v1.Volume{
		Name: "docker-sock",
		VolumeSource: v1.VolumeSource{
			HostPath: &v1.HostPathVolumeSource{
				Path: "/var/run/docker.sock",
				Type: &sockPathType,
			},
		},
	})*/

	if unit.ResultFile != "" && path.Base(unit.ResultFile) != "" {
		guid := uuid.New()
		uuid := guid.String()
		uuid = strings.ReplaceAll(uuid, "-", "")[:10]
		volumes = append(volumes,
			v1.Volume{
				Name: "result-pvc-dir",
				VolumeSource: v1.VolumeSource{
					HostPath: &v1.HostPathVolumeSource{
						Path: path.Join("/tmp/t",unit.DataDir, unit.Runners[0].Envs["JM_SHARE_DIR"]+"-"+uuid),
						Type: &hostPathType,
					},
				},
			})

		if unit.K8sNode {
			hostPathType := v1.HostPathDirectoryOrCreate
			volumes = append(volumes,
				v1.Volume{
					Name: "result-pvc",
					VolumeSource: v1.VolumeSource{
						HostPath: &v1.HostPathVolumeSource{
							Path: path.Join("/tmp/t",unit.DataDir, unit.Runners[0].Envs["JM_SHARE_DIR"]+"-"+uuid),
							Type: &hostPathType,
						},
					},
				})
		} else {
			hostPathType := v1.HostPathFileOrCreate
			volumes = append(volumes,
				v1.Volume{
					Name: "result-pvc",
					VolumeSource: v1.VolumeSource{
						HostPath: &v1.HostPathVolumeSource{
							Path: path.Join("/tmp/t",unit.DataDir, unit.Runners[0].Envs["JM_SHARE_DIR"]+"-"+uuid, path.Base(unit.ResultFile)),
							Type: &hostPathType,
						},
					},
				})
		}
	}


        volumes = append(volumes,
                                v1.Volume{
                                        Name: "customm2",
                                        VolumeSource: v1.VolumeSource{
                                                HostPath: &v1.HostPathVolumeSource{
                                                        Path: path.Join("/opt/maven/","repo"),
                                                        Type: &hostPathType,
                                                },
                                        },
                             })

	return volumes
}

func toDockerConfigSecret(unit *Unit) *v1.Secret {
	return &v1.Secret{
		ObjectMeta: metav1.ObjectMeta{
			Name: unit.PullSecret.Name,
		},
		Type: "kubernetes.io/dockerconfigjson",
		StringData: map[string]string{
			".dockerconfigjson": unit.PullSecret.Data,
		},
	}
}

func toSecret(unit *Unit) *v1.Secret {
	stringData := make(map[string]string)
	for _, secret := range unit.Secrets {
		stringData[secret.Name] = secret.Data
	}

	return &v1.Secret{
		ObjectMeta: metav1.ObjectMeta{
			Name: unit.PodSpec.Name,
		},
		Type:       "Opaque",
		StringData: stringData,
	}
}

func toConfigMap(unit *Unit) *v1.ConfigMap {
	stringData := make(map[string]string)
	for _, runner := range unit.Runners {
		for k, v := range runner.Envs {
			stringData[unit.PodSpec.Name+"."+k] = v
		}
	}

	return &v1.ConfigMap{
		ObjectMeta: metav1.ObjectMeta{
			Name: unit.PodSpec.Name,
		},
		Data: stringData,
	}
}

func boolptr(v bool) *bool {
	return &v
}
